/**
 * This file is part of Idegen, the application to create IDE project files.
 *
 * @copyright Copyright (c) 2011-2012, Walter Tamboer http://waltertamboer.nl
 * @file Idegen/Application/Settings.cpp
 */

#include "Idegen/PrecompiledHeader.hpp"
#include "Idegen/Idegen/IdegenGeneratorType.hpp"
#include "Idegen/Idegen/IdegenGenerator.hpp"
#include "Idegen/Idegen/IdegenProject.hpp"
#include "Idegen/Idegen/IdegenProjectConfig.hpp"
#include "Idegen/Idegen/IdegenProjectFilter.hpp"
#include "Idegen/Idegen/IdegenSourceSet.hpp"
#include "Idegen/Application/Settings.hpp"
#include "Idegen/IO/FileSystem.hpp"

namespace Idegen
{
	bool ParseAttributeString( TiXmlElement* element, const std::string& name, std::string& value )
	{
		bool result = false;

		TiXmlAttribute* attrib = element->FirstAttribute();
		while ( attrib != NULL )
		{
			if ( std::string( attrib->Name() ) == name )
			{
				value = attrib->Value();
				result = true;
				break;
			}

			attrib = attrib->Next();
		}

		return result;
	}

	bool ParseAttributeInt( TiXmlElement* element, const std::string& name, int& value )
	{
		return element->QueryIntAttribute( name.c_str(), &value ) == TIXML_SUCCESS;
	}
	
	bool ParseElementBoolean( TiXmlElement* element, bool& value )
	{
		if ( element->FirstChild() != NULL )
		{
			std::string text = element->FirstChild()->Value();
			std::transform( text.begin(), text.end(), text.begin(), ::tolower );

			value = text == "true" || text == "yes" || text == "1";
		}
		return ( element->FirstChild() != NULL );
	}

	bool ParseElementFloat( TiXmlElement* element, float& value )
	{
		if ( element->FirstChild() != NULL )
		{
			std::stringstream ss = std::stringstream( element->FirstChild()->Value() );

			ss >> value;
		}
		return ( element->FirstChild() != NULL );
	}

	bool ParseElementInt( TiXmlElement* element, int& value )
	{
		if ( element->FirstChild() != NULL )
		{
			std::stringstream ss = std::stringstream( element->FirstChild()->Value() );

			ss >> value;
		}
		return ( element->FirstChild() != NULL );
	}

	bool ParseElementString( TiXmlElement* element, std::string& value )
	{
		if ( element->FirstChild() != NULL )
		{
			value = element->FirstChild()->Value();
		}
		return ( element->FirstChild() != NULL );
	}

	Settings::Settings( const std::string& settingsFile )
	{
		m_BasePath = GetStartupDirectory();
		Load( settingsFile );
	}

	Settings::Settings( const std::string& settingsFile, const std::string& basePath )
	{
		m_BasePath = basePath;
		Load( settingsFile );
	}

	Settings::~Settings()
	{
		for ( IdegenGeneratorList::iterator it = m_Generators.begin(); it != m_Generators.end(); ++it )
		{
			delete (*it);
		}
		for ( IdegenProjectList::iterator it = m_Projects.begin(); it != m_Projects.end(); ++it )
		{
			delete (*it);
		}
		for ( IdegenSourceSetList::iterator it = m_Sources.begin(); it != m_Sources.end(); ++it )
		{
			delete (*it);
		}
		m_Generators.clear();
	}

	IdegenGenerator* Settings::FindGenerator( const std::string& name ) const
	{
		IdegenGenerator* result = NULL;

		for ( IdegenGeneratorList::const_iterator it = m_Generators.begin(); it != m_Generators.end(); ++it )
		{
			if ( (*it)->GetName() == name )
			{
				result = *it;
				break;
			}
		}

		return result;
	}

	IdegenProject* Settings::FindProject( const std::string& name ) const
	{
		IdegenProject* result = NULL;

		for ( IdegenProjectList::const_iterator it = m_Projects.begin(); it != m_Projects.end(); ++it )
		{
			if ( (*it)->GetName() == name )
			{
				result = *it;
				break;
			}
		}

		return result;
	}

	std::string Settings::GetBasePath() const
	{
		return m_BasePath;
	}

	IdegenGeneratorList Settings::GetGenerators() const
	{
		return m_Generators;
	}

	SettingsScope* Settings::GetScope()
	{
		return &m_Scope;
	}

	void Settings::Load( const std::string& path )
	{
		TiXmlDocument document( path.c_str() );

		if ( document.LoadFile() )
		{
			m_RootElement = document.FirstChildElement( "idegen" );
			if ( m_RootElement != NULL )
			{
				ParseIncludes( m_RootElement );

				// Parse all the generators:
				TiXmlElement* element = m_RootElement->FirstChildElement( "i:generator" );
				while ( element != NULL )
				{
					// Create an unserialize the generator:
					m_Scope.Generator = new IdegenGenerator();
					m_Scope.Generator->Unserialize( this, element );

					// Register it:
					m_Generators.push_back( m_Scope.Generator );

					// On to the next one:
					element = element->NextSiblingElement( "i:generator" );
				}
			}
		}
	}

	void Settings::ParseIncludes( TiXmlElement* parent )
	{
		TiXmlElement* element = parent->FirstChildElement( "i:include" );
		while ( element != NULL )
		{
			std::string path;
			if ( ParseAttributeString( element, "path", path ) )
			{
				// Parse the variables and convert the path to an absolute path:
				path = ParseVariables( path );
				path = RelativeToAbsolutePath( path, m_BasePath, false );
				
				TiXmlDocument document( path.c_str() );
				if ( document.LoadFile() )
				{
					TiXmlElement* rootElement = document.FirstChildElement( "idegen" );
					if ( rootElement != NULL )
					{
						TiXmlElement* element = rootElement->FirstChildElement();
						while ( element != NULL )
						{
							parent->LinkEndChild( element->Clone() );
							element = element->NextSiblingElement();
						}
					}
				}
			}
			element = element->NextSiblingElement( "i:include" );
		}
	}
	
	IdegenProject* Settings::ParseProject( const std::string& name )
	{
		IdegenProject* result = NULL;

		TiXmlElement* element = m_RootElement->FirstChildElement( "i:project" );
		while ( element != NULL )
		{
			std::string attribValue;
			if ( ParseAttributeString( element, "name", attribValue ) )
			{
				if ( attribValue == name )
				{
					result = ParseProject( element );
					break;
				}
			}

			if ( result != NULL )
			{
				// Break out the loop since we found the project:
				break;
			}

			element = element->NextSiblingElement( "i:project" );
		}

		return result;
	}
	
	IdegenProject* Settings::ParseProject( TiXmlElement* element )
	{
		m_Scope.Project = new IdegenProject();
		m_Scope.Project->Unserialize( this, element );

		m_Projects.push_back( m_Scope.Project );

		return m_Scope.Project;
	}
	
	IdegenSourceSet* Settings::ParseSourceSet( const std::string& name )
	{
		return ParseSourceSet( name, m_RootElement );
	}
	
	IdegenSourceSet* Settings::ParseSourceSet( const std::string& name, TiXmlElement* parent )
	{
		IdegenSourceSet* result = NULL;

		TiXmlElement* element = parent->FirstChildElement( "i:source" );
		while ( element != NULL )
		{
			std::string attribValue;
			if ( ParseAttributeString( element, "name", attribValue ) )
			{
				if ( attribValue == name && element->FirstChildElement() != NULL )
				{
					result = ParseSourceSet( element );
					break;
				}
			}

			if ( result != NULL )
			{
				// Break out the loop since we found the project:
				break;
			}

			element = element->NextSiblingElement( "i:source" );
		}

		return result;
	}
	
	IdegenSourceSet* Settings::ParseSourceSet( TiXmlElement* element )
	{
		IdegenSourceSet* result = new IdegenSourceSet();
		result->Unserialize( this, element );

		m_Sources.push_back( result );

		return result;
	}

	void SkipWhitespace( std::string::iterator& begin, std::string::iterator end )
	{
		while ( begin != end && *begin == ' ' )
		{
			++begin;
		}
	}

	std::string ParseVariableString( std::string::iterator& begin, std::string::iterator end )
	{
		int escaped = 0;

		std::string result;
		if ( *begin == '"' )
		{
			++begin; // The char: "
			while ( begin != end && ( *begin != '"' && escaped == 0 ) )
			{
				result += *begin;
				++begin;
			}
			++begin; // The char: "
		}
		else if ( *begin == '\'' )
		{
			++begin; // The char: '
			while ( begin != end && ( *begin != '\'' && escaped == 0 ) )
			{
				result += *begin;
				++begin;
			}
			++begin; // The char: '
		}
		return result;
	}

	std::string Settings::ParseVariableFunction( std::string::iterator& begin, std::string::iterator end )
	{
		std::string result;
		std::string functionName;

		// First we parse the function name:
		while ( begin != end && *begin != '(' )
		{
			functionName += *begin;
			++begin;
		}

		// Depending on the function we parse arguments:
		if ( functionName == "Replace" )
		{
			++begin; // The char: (

			// Parse the first argument:
			std::string replaceWhat = ParseVariableString( begin, end );

			// Skip all whitespace and the comma:
			SkipWhitespace( begin, end );
			begin += 1;
			SkipWhitespace( begin, end );
			
			// Parse the second argument:
			std::string replaceWith = ParseVariableString( begin, end );
			
			// Skip all whitespace and the comma:
			SkipWhitespace( begin, end );
			begin += 1;
			SkipWhitespace( begin, end );

			// Now parse the variable name:
			result = ParseVariableName( begin, end );
			std::replace( result.begin(), result.end(), replaceWhat[ 0 ], replaceWith[ 0 ] );
		}

		return result;
	}

	std::string Settings::ParseVariableName( std::string::iterator& begin, std::string::iterator end )
	{
		std::string variableName;
		while ( begin != end && *begin != ')' )
		{
			variableName += *begin;
			++begin;
		}

		std::string result;
		if ( variableName == "GeneratorName" && m_Scope.Generator != NULL )
		{
			result = m_Scope.Generator->GetName();
		}
		else if ( variableName == "ConfigName" && m_Scope.Config != NULL )
		{
			result = m_Scope.Config->GetName();
		}
		else if ( variableName == "ConfigPlatform" && m_Scope.Config != NULL )
		{
			result = m_Scope.Config->GetPlatform();
		}
		else if ( variableName == "ProjectName" && m_Scope.Project != NULL )
		{
			result = m_Scope.Project->GetName();
		}
		return result;
	}

	std::string Settings::ParseVariables( const std::string& value )
	{
		std::string result;

		std::string temp = value;

		for ( std::string::iterator it = temp.begin(); it != temp.end(); ++it )
		{
			if ( *it == '$' )
			{
				++it;
				if ( *it == '(' )
				{
					it += 1;
					result += ParseVariableName( it, temp.end() );
				}
				else
				{
					result += ParseVariableFunction( it, temp.end() );
				}
			}
			else
			{
				result += *it;
			}
		}

		return result;
	}
}
